#!/usr/bin/env python
#
# GoSdrClients - User Interface for Control configuration
# 
# User: bob
# Date: 21/12/13
# Copyright (C) 2013 by G3UKB Bob Cowdery
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#    
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#    
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#    
#  The author can be reached by email at:   
#     bob@bobcowdery.plus.com
#

# System imports
import os,sys, traceback
import copy
from PyQt4 import QtCore, QtGui
        
# Application imports
from ui.control.control_common import *
import common.instance_cache as instance_cache

"""
Configuration dialog
"""
class ConfigDialog(QtGui.QDialog):
    
    def __init__(self, settings, apply_callback, parent = None):
        """
        Constructor
        
        Arguments:
            settings        --  dictionary of current settings
            apply_callback  --  callback here to apply settings
            parent          --  parent window
        
        Settings are of the following form:    
        DEFAULT_CONFIG: {
            COMMON: {
                IN_RATE: 48000,
                OUT_RATE: 48000,
                NUM_RX: 1,
                IQ_BLK_SZ: 1024,
                MIC_BLK_SZ: 1024,
                DUPLEX: False,
                TX: False,
                POWER: {
                    PWR_MODE: AUTO,
                    PWR_IP: '192.168.1.100',
                    PWR_USER: 'admin',
                    PWR_PW: '12345678',
                    PWR_OUTLET: 1,
                },
                AUDIO: {
                    IN_SRC: HPSDR,
                    IN_HOSTAPI: '',
                    IN_DEV: '',
                    OUT_SINK: HPSDR,
                    ROUTING: {
                        HPSDR:[[1, '', '', BOTH],],
                        LOCAL: []   
                    }
                },
                ANTENNA: {
                    ANT_SETTING: {ANT_1: [MIN_FREQ, MAX_FREQ],},
                    ANT_PREC: 'default'
                }
            },
            RX: {
                DISPLAYS: {
                    PAN_MODE: 3,
                    PAN_FRM_AV: 30,
                    WINDOW_TYPE: 5,
                    PAN_FFT_SZ: 4096
                }
            }
            TX: {
                LEVELS: {
                    TUNE_DRIVE_LEVEL: 20
                }
            } 
        }
        
        """
        
        super(ConfigDialog, self).__init__(parent)
        
        self.__apply_callback = apply_callback
        
        # Get audio api
        self.__audio = instance_cache.getInstance('Audio_API');
        self.setWindowTitle('Configure')
        
        # Class vars
        # All settings
        self.__orig_settings = settings
        self.__settings = copy.deepcopy(settings)
        self.__suppress_events = False
        # What changed?
        self.__hints = []
        
        # General settings
        self.IQ_BLK_SZ = ('4096','2048','1024','512','256')
        self.IN_RATE = ('48000','96000','192000','384000')
        
        # Audio settings
        self.SRC_SINK = (HPSDR, LOCAL)        
        self.__sink_devs = {}   # Sink list
        
        # Power settings
        self.MODE = (AUTO, PROMPT, MANUAL, OFF)
        self.OUTLET = ('1', '2', '3', '4')
        
        # Display settings
        self.OVER_FRMS = ('10','20','30','40','50','60')
        self.FFT_SZ = ('4096','8192','16384','32768','65536')
       
        #======================================================================
        # UI Configuration
        # Top level tabs
        self.top_tab_widget = QtGui.QTabWidget()
        top_tab1 = QtGui.QWidget()
        top_tab2 = QtGui.QWidget()
        top_tab3 = QtGui.QWidget()
        self.top_tab_widget.addTab(top_tab1, "Common")
        self.top_tab_widget.addTab(top_tab2, "Receiver")
        self.top_tab_widget.addTab(top_tab3, "Transmitter")
        
        # Tabs for the common pages
        self.common_tab_widget = QtGui.QTabWidget()
        common_tab1 = QtGui.QWidget()
        common_tab2 = QtGui.QWidget()
        common_tab3 = QtGui.QWidget()
        common_tab4 = QtGui.QWidget()
        self.common_tab_widget.addTab(common_tab1, "General")
        self.common_tab_widget.addTab(common_tab2, "Audio routing")
        self.common_tab_widget.addTab(common_tab3, "IP Power")
        self.common_tab_widget.addTab(common_tab4, "Antenna")
        
        # Tabs for the receiver pages
        self.rx_tab_widget = QtGui.QTabWidget()
        rx_tab1 = QtGui.QWidget()
        self.rx_tab_widget.addTab(rx_tab1, "Display")
    
        # Tabs for the transmitter pages
        self.tx_tab_widget = QtGui.QTabWidget()
        tx_tab1 = QtGui.QWidget()
        self.tx_tab_widget.addTab(tx_tab1, "Levels")
        
        # Add the top layout to the dialog
        top_layout = QtGui.QGridLayout(self)
        top_layout.addWidget(self.top_tab_widget, 0, 0)
        self.setLayout(top_layout)
        
        # Set layouts for top tab
        top_grid1 = QtGui.QGridLayout()
        top_grid2 = QtGui.QGridLayout()
        top_grid3 = QtGui.QGridLayout() 
        top_tab1.setLayout(top_grid1)
        top_tab2.setLayout(top_grid2)
        top_tab3.setLayout(top_grid3)
        top_grid1.addWidget(self.common_tab_widget, 0, 0)
        top_grid2.addWidget(self.rx_tab_widget, 0, 0)
        top_grid3.addWidget(self.tx_tab_widget, 0, 0)
        
        # Common pages ========================================================
        # Set layouts for common pages
        common_grid1 = QtGui.QGridLayout()
        common_grid2 = QtGui.QGridLayout()
        common_grid3 = QtGui.QGridLayout()
        common_grid4 = QtGui.QGridLayout() 
        common_tab1.setLayout(common_grid1)
        common_tab2.setLayout(common_grid2)
        common_tab3.setLayout(common_grid3)
        common_tab4.setLayout(common_grid4)
               
        # Populate general page
        self.__populate_general(common_grid1)
        # Populate audio routing page
        self.__populate_audio_routing(common_grid2)
        # Populate IP power page
        self.__populate_ip_power(common_grid3)
        # Populate Antenna Selection
        self.__populate_antenna(common_grid4)
        
        # Receiver pages ======================================================
        # Set layouts for receiver pages
        rx_grid1 = QtGui.QGridLayout()
        rx_tab1.setLayout(rx_grid1)
               
        # Populate displays page
        self.__populate_rx_displays(rx_grid1)
        
        # Transmitter pages ===================================================
        # Set layouts for transmitter pages
        tx_grid1 = QtGui.QGridLayout()
        tx_tab1.setLayout(tx_grid1)
               
        # Populate levels page
        self.__populate_tx_levels(tx_grid1)
        
        # Buttons =============================================================
        # OK, Apply and Cancel buttons
        self.buttons = QtGui.QDialogButtonBox(
            QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel,
            QtCore.Qt.Horizontal, self)
        top_layout.addWidget(self.buttons, 1, 0)
        #self.buttons.clicked.connect(self.__apply)
        self.buttons.accepted.connect(self.accept)
        self.buttons.rejected.connect(self.reject)
        
        applyButton = self.buttons.button(QtGui.QDialogButtonBox.Apply)
        QtCore.QObject.connect(applyButton, QtCore.SIGNAL("clicked()"), self.__apply)
        
    def response(self, result):
        
        if result:
            # Ok
            # Return new settings
            return copy.deepcopy(self.__settings)
        else:
            # Cancel
            # Reinstate the original settings
            self.__settings = copy.deepcopy(self.__orig_settings)
            return self.__orig_settings

    # Static method to create the dialog and return response
    @staticmethod
    def getConfig(settings, apply_callback, parent = None):
        """ Start a new dialog session """
        
        dialog = ConfigDialog(settings, apply_callback, parent)
        result = dialog.exec_()
        response = dialog.response(result == QtGui.QDialog.Accepted)
        return response, result == QtGui.QDialog.Accepted
    
    # PRIVATE =================================================================
    #==========================================================================
    def __apply(self):
        """ Apply changes """
        
        unique_hints = []
        [unique_hints.append(item) for item in self.__hints if item not in unique_hints]
        self.__apply_callback(copy.deepcopy(self.__settings), unique_hints)
           
    def __populate_general(self, layout):
       
        # This will force the visible rows to no stretch
        # and give col 1 all the available space
        layout.setRowStretch(5,10)
        layout.setColumnStretch(1,10)
        
        # Rate selection
        rate_label = QtGui.QLabel('Smpl rate')
        layout.addWidget(rate_label, 0, 0)
        rate_combo = QtGui.QComboBox()
        rate_combo.addItems(self.IN_RATE)
        rate_combo.setCurrentIndex(self.IN_RATE.index(str(self.__settings[COMMON][IN_RATE])))
        layout.addWidget(rate_combo, 0, 1)
        rate_combo.activated.connect(self.on_rate)
        #rate_combo.setEnabled(False)

       # Number of receivers
        no_rx_label = QtGui.QLabel('Num RX')
        layout.addWidget(no_rx_label, 1, 0)
        no_rx_spin = QtGui.QSpinBox()
        no_rx_spin.setRange(1, 7)
        no_rx_spin.setValue(self.__settings[COMMON][NUM_RX])
        layout.addWidget(no_rx_spin, 1, 1)
        no_rx_spin.valueChanged.connect(self.on_num_rx)
        
       # Block size selection
        blk_sz_label = QtGui.QLabel('Blk sz')
        layout.addWidget(blk_sz_label, 2, 0)
        blk_sz_combo = QtGui.QComboBox()
        blk_sz_combo.addItems(self.IQ_BLK_SZ)
        blk_sz_combo.setCurrentIndex(self.IQ_BLK_SZ.index(str(self.__settings[COMMON][IQ_BLK_SZ])))
        layout.addWidget(blk_sz_combo, 2, 1)
        blk_sz_combo.activated.connect(self.on_blk_sz)
        
        # Duplex selection
        duplex_label = QtGui.QLabel('Duplex')
        layout.addWidget(duplex_label, 3, 0)
        duplex_cb = QtGui.QCheckBox('')
        layout.addWidget(duplex_cb, 3, 1)
        duplex_cb.stateChanged.connect(self.on_duplex)
        duplex_cb.setChecked(self.__settings[COMMON][DUPLEX])
        
        # TX selection
        tx_label = QtGui.QLabel('Enable TX')
        layout.addWidget(tx_label, 4, 0)
        tx_cb = QtGui.QCheckBox('')
        layout.addWidget(tx_cb, 4, 1)
        tx_cb.stateChanged.connect(self.on_tx)
        tx_cb.setChecked(self.__settings[COMMON][TX])
    
    def on_rate(self, sel):
        
        self.__settings[COMMON][IN_RATE] = int(self.IN_RATE[sel])
        # Set the MIC_BLK_SZ according to sample rates
        self.__setMicBlkSz()
        self.__hints.append(RATE_EVNT)

    def on_num_rx(self, value):
        
        self.__settings[COMMON][NUM_RX] = value
        self.__hints.append(NUM_RX_EVNT)
    
    def on_blk_sz(self, sel):
        
        self.__settings[COMMON][IQ_BLK_SZ] = int(self.IQ_BLK_SZ[sel])
        # Set the MIC_BLK_SZ according to sample rates
        self.__setMicBlkSz()
        self.__hints.append(BLK_SZ_EVNT)
    
    def on_duplex(self, state):
        
        if state == QtCore.Qt.Checked:
            self.__settings[COMMON][DUPLEX] = True
        else:
            self.__settings[COMMON][DUPLEX] = False
        self.__hints.append(DUPLEX_EVNT)
    
    def on_tx(self, state):
        
        if state == QtCore.Qt.Checked:
            self.__settings[COMMON][TX] = True
        else:
            self.__settings[COMMON][TX] = False
        self.__hints.append(TX_ENABLE_EVNT)
     
    def __setMicBlkSz(self):
        
        self.__settings[COMMON][MIC_BLK_SZ] = int(float(self.__settings[COMMON][IQ_BLK_SZ]) * (float(self.__settings[COMMON][OUT_RATE])/float(self.__settings[COMMON][IN_RATE])))
                                                  
    #========================================================================== 
    def __populate_audio_routing(self, layout):
        
        # Input source
        # Select HPSDR or Local
        in_src_label = QtGui.QLabel('Input source')
        layout.addWidget(in_src_label, 0, 0)
        self.src_combo = QtGui.QComboBox()
        self.src_combo.addItems(self.SRC_SINK)
        layout.addWidget(self.src_combo, 0, 1)
        self.src_combo.activated.connect(self.on_src_select)
        # Select the audio device
        in_dev_label = QtGui.QLabel('Input device')
        layout.addWidget(in_dev_label, 1, 0)
        self.src_dev_combo = QtGui.QComboBox()
        input_list = self.__audio.enum_input_devices()
        in_device_names = []
        for device in input_list:
            in_device_names.append('%s-%s' % (device['hostapi'], device['name']))
        self.src_dev_combo.addItems(in_device_names)
        layout.addWidget(self.src_dev_combo, 1, 1)
        self.src_dev_combo.activated.connect(self.on_src_device)
        
        # Draw a line
        frame = QtGui.QFrame()
        frame.setFrameShape( QtGui.QFrame.HLine)
        frame.setFrameShadow(QtGui.QFrame.Sunken)
        layout.addWidget(frame, 2, 0, 1, 2)
                
        # Output sinks
        # Select HPSDR or Local
        out_src_label = QtGui.QLabel('Output sink')
        layout.addWidget(out_src_label, 3, 0)
        self.sink_combo = QtGui.QComboBox()
        self.sink_combo.addItems(self.SRC_SINK)
        layout.addWidget(self.sink_combo, 3, 1)
        self.sink_combo.activated.connect(self.on_sink_select)
        # Assign receiver
        out_rx_label = QtGui.QLabel('Receiver')
        layout.addWidget(out_rx_label, 4, 0)
        self.out_rx_combo = QtGui.QComboBox()
        out_rx_ids = ['1','2','3','4','5','6','7','8']
        self.out_rx_combo.addItems(out_rx_ids)
        # No selection required here, the first in the list is the default selection
        layout.addWidget(self.out_rx_combo, 4, 1)
        # Select the audio device
        out_dev_label = QtGui.QLabel('Output device')
        layout.addWidget(out_dev_label, 5, 0)
        self.__out_dev_combo = QtGui.QComboBox()
        output_list = self.__audio.enum_output_devices()
        out_device_names = []
        for device in output_list:
            out_device_names.append('%s-%s' % (device['hostapi'], device['name']))
        self.__out_dev_combo.addItems(out_device_names)
        layout.addWidget(self.__out_dev_combo, 5, 1)
        # Assign channel
        ch_out_label = QtGui.QLabel('Ch')
        layout.addWidget(ch_out_label, 6, 0)
        self.__out_ch_combo = QtGui.QComboBox()
        ch_out = ['Left', 'Right', 'Both']
        self.__out_ch_combo.addItems(ch_out)
        layout.addWidget(self.__out_ch_combo, 6, 1)
        
        # 'Add' button
        addbtn = QtGui.QPushButton('Add')
        addbtn.setToolTip('Add this device/rx to output list')
        addbtn.resize(addbtn.sizeHint())
        addbtn.setEnabled(True)
        layout.addWidget(addbtn, 7, 0)
        addbtn.clicked.connect(self.add_audio_sink)
        
        # Selected list
        self.__sink_list = QtGui.QListWidget()
        self.__sink_list.setMaximumHeight(100)
        layout.addWidget(self.__sink_list, 8, 0, 1, 2)
        
        # 'Remove' button
        self.delbtn = QtGui.QPushButton('Remove')
        self.delbtn.setToolTip('Remove this device/rx from output list')
        self.delbtn.resize(self.delbtn.sizeHint())
        self.delbtn.setEnabled(False)
        layout.addWidget(self.delbtn, 9, 0)
        self.delbtn.clicked.connect(self.remove_audio_sink)
        
        # Draw a line
        frame = QtGui.QFrame()
        frame.setFrameShape( QtGui.QFrame.HLine)
        frame.setFrameShadow(QtGui.QFrame.Sunken)
        layout.addWidget(frame, 10, 0, 1, 2)
        
        # Populate from last known state
        self.src_combo.setCurrentIndex(self.SRC_SINK.index(self.__settings[COMMON][AUDIO][IN_SRC]))
        if len(self.__settings[COMMON][AUDIO][IN_DEV]) > 0:
            index = self.src_dev_combo.findText('%s-%s' % (self.__settings[COMMON][AUDIO][IN_HOSTAPI], self.__settings[COMMON][AUDIO][IN_DEV]))
        else:
            index = 0
        self.src_dev_combo.setCurrentIndex(index)
        
        self.sink_combo.setCurrentIndex(self.SRC_SINK.index(self.__settings[COMMON][AUDIO][OUT_SINK]))
        # Add the list items
        if self.sink_combo.currentText() == HPSDR:
            sink = HPSDR
            self.__out_dev_combo.setEnabled(False)
            routes = self.__settings[COMMON][AUDIO][ROUTING][HPSDR]
        else:
            sink = LOCAL
            routes = self.__settings[COMMON][AUDIO][ROUTING][LOCAL]
        # Add the appropriate routes to the list    
        for route in routes:
            self.__add_audio_sink(sink, route[0], route[1], route[2], route[3], True)
     
    # Event handlers   
    def on_src_select(self):
        
        self.__settings[COMMON][AUDIO][IN_SRC] = self.src_combo.currentText()
        self.__hints.append(AUDIO_CH_EVNT)
        
    def on_src_device(self):
        
        hostapi, dev = self.src_dev_combo.currentText().split('-')
        self.__settings[COMMON][AUDIO][IN_HOSTAPI] = hostapi
        self.__settings[COMMON][AUDIO][IN_DEV] = dev
        self.__hints.append(AUDIO_CH_EVNT)
    
    def on_sink_select(self):
        
        self.__settings[COMMON][AUDIO][OUT_SINK] = self.sink_combo.currentText()
        # Add the list items
        if self.sink_combo.currentText() == HPSDR:
            sink = HPSDR
            self.__out_dev_combo.setEnabled(False)
            routes = self.__settings[COMMON][AUDIO][ROUTING][HPSDR]
        else:
            sink = LOCAL
            self.__out_dev_combo.setEnabled(True)
            routes = self.__settings[COMMON][AUDIO][ROUTING][LOCAL]
        # Update the route list
        self.__sink_list.clear()
        for route in routes:
            self.__add_audio_sink(sink, route[0], route[1], route[2], route[3], True)
        self.__hints.append(AUDIO_CH_EVNT)
        
    def add_audio_sink(self):
        """ Add this sink to the state and the user list """
        
        sink = self.sink_combo.currentText()
        rx = int(self.out_rx_combo.currentText())
        hostapi, device = self.__out_dev_combo.currentText().split('-')
        ch = self.__out_ch_combo.currentText()
        
        self.__add_audio_sink(sink, rx, hostapi, device, ch)
        self.__hints.append(AUDIO_CH_EVNT)
        
    def remove_audio_sink(self):
        """ Remove this sink from the state and the user list """
        
        sink = self.sink_combo.currentText()
        current_row = self.__sink_list.currentRow()
        current_text = self.__sink_list.currentItem().text()
        toks = current_text.split(',')
        rx = int(toks[0].split(':')[1])
        host_dev = toks[1].split(':')[1]
        hostapi, dev =  host_dev.split('-')
        ch = toks[2].split(':')[1]
        self.__remove_sink(current_row, sink, rx, hostapi, dev, ch)
        self.__hints.append(AUDIO_CH_EVNT)
        
    # Private 
    def __add_audio_sink(self, sink, rx, hostapi, device, ch, refresh=False):
        
        # Add this to the collection and the user list
        if sink == HPSDR:
            routes = self.__settings[COMMON][AUDIO][ROUTING][HPSDR]
        else:
            routes = self.__settings[COMMON][AUDIO][ROUTING][LOCAL]
        if refresh:
            # Updating list
            self.__sink_list.addItem('RX:%s, DEV:%s-%s, CH:%s' % (rx, hostapi, device, ch))
            self.delbtn.setEnabled(True)
        else:
            # Adding item
            allocated = [x for x in routes if x[0] == rx]
            if len(allocated) == 0:
                # Is this left or right channel used elsewhere
                if self.__in_use(routes, hostapi, device, ch):
                    QtGui.QMessageBox.information(self, 'Audio assignment','Sorry, the device/channel is already in use on another receiver', QtGui.QMessageBox.Ok)
                    return
            else:
                QtGui.QMessageBox.information(self, 'Audio assignment','Sorry, that receiver is already assigned', QtGui.QMessageBox.Ok)
                return
                
            # Add the new device entry
            routes.append([rx, hostapi, device, ch])
            self.__sink_list.addItem('RX:%s, DEV:%s-%s, CH:%s' % (rx, hostapi, device, ch))
            self.delbtn.setEnabled(True)
            self.__hints.append(AUDIO_CH_EVNT)
                    
    def __in_use(self, routes, hostapi, device, ch):
        """
        Check if this device/ch is in use
        
        Arguments:
            routes  --  route structure to as input
            hostapi --  qualfier to device
            device  --  device to check against
            ch      --  channel to check against 
            
        """
        
        for route in routes:
            rx, this_host, this_dev, this_ch = route
            if this_dev == device and this_host == hostapi: 
                if this_ch == BOTH:
                    return True
                elif this_ch == LEFT and (ch == LEFT or ch == BOTH):
                    return True
                elif this_ch == RIGHT and (ch == RIGHT or ch == BOTH):
                    return True
        return False
                   
    def __remove_sink(self, row, sink, rx, hostapi, dev, ch):
        """
        Remove the sink from the state and user interface
        
        Arguments:
            row     --  the selected row in the sink list
            sink    --  the sink type (HPSDR | LOCAL)
            rx      --  rx id
            hostapi --  qualifier to device
            dev     --  device name
            ch      -- channel (LEFT | RIGHT | BOTH)
            
        """
        
        if sink == HPSDR:
            routes = self.__settings[COMMON][AUDIO][ROUTING][HPSDR]
        else:
            routes = self.__settings[COMMON][AUDIO][ROUTING][LOCAL]
        
        for this_rx, this_hostapi, this_dev, this_ch in routes:
            if (this_rx, this_hostapi, this_dev, this_ch) == (rx, hostapi, dev, ch):
                routes.remove([this_rx, this_hostapi, this_dev, this_ch])
        self.__sink_list.takeItem(row)                            
    
    #==========================================================================    
    def __populate_ip_power(self, layout):
        
        # This will force the visible rows to no stretch
        # and give row 1 all the available space
        layout.setRowStretch(5,10)
        layout.setColumnStretch(1,10)
        
        # Activation mode
        mode_label = QtGui.QLabel('Mode')
        layout.addWidget(mode_label, 0, 0)
        mode_combo = QtGui.QComboBox()
        mode_combo.addItems(self.MODE)
        mode_combo.setCurrentIndex(self.MODE.index(str(self.__settings[COMMON][POWER][PWR_MODE])))
        layout.addWidget(mode_combo, 0, 1)
        mode_combo.activated.connect(self.on_pwr_mode)
        
        # Host address
        ip_label = QtGui.QLabel('Host')
        layout.addWidget(ip_label, 1, 0)
        ip_text = QtGui.QLineEdit()
        ip_text.setInputMask('000.000.000.000;_')
        ip_text.setText(self.__settings[COMMON][POWER][PWR_IP])
        layout.addWidget(ip_text, 1, 1)
        ip_text.textChanged.connect(self.on_ip_addr)
        
        # Credentials - user
        user_label = QtGui.QLabel('User')
        layout.addWidget(user_label, 2, 0)
        user_text = QtGui.QLineEdit()
        user_text.setText(self.__settings[COMMON][POWER][PWR_USER])
        layout.addWidget(user_text, 2, 1)
        user_text.textChanged.connect(self.on_user)
        
        # Credentials - password
        pw_label = QtGui.QLabel('Password')
        layout.addWidget(pw_label, 3, 0)
        pw_text = QtGui.QLineEdit()
        pw_text.setText(self.__settings[COMMON][POWER][PWR_PW])
        layout.addWidget(pw_text, 3, 1)
        pw_text.textChanged.connect(self.on_password)
        
        # Outlet 1-4
        outlet_label = QtGui.QLabel('Outlet')
        layout.addWidget(outlet_label, 4, 0)
        outlet_combo = QtGui.QComboBox()
        outlet_combo.addItems(self.OUTLET)
        outlet_combo.setCurrentIndex(self.OUTLET.index(str(self.__settings[COMMON][POWER][PWR_OUTLET])))
        layout.addWidget(outlet_combo, 4, 1)
        outlet_combo.activated.connect(self.on_pwr_outlet)
    
    def on_pwr_mode(self, sel):
        
        self.__settings[COMMON][POWER][PWR_MODE] = self.MODE[sel]
        
    def on_ip_addr(self, text):
        
        self.__settings[COMMON][POWER][PWR_IP] = text
        
    def on_user(self, text):
        
        self.__settings[COMMON][POWER][PWR_USER] = text
        
    def on_password(self, text):
        
        self.__settings[COMMON][POWER][PWR_PW] = text
    
    def on_pwr_outlet(self, sel):
        
        self.__settings[COMMON][POWER][PWR_OUTLET] = int(self.OUTLET[sel])

    #==========================================================================  
    def __populate_antenna(self, layout):
        
        # Antenna selection is as follows:
        #   1. RX only antenna - None, RX1, RX2, TVR
        #   2. Transceive antenna - Ant1, Ant2, Ant3
        #   3. RX out selection can loop back into any RX Ant.
        #
        # If an RX only ant is selected this overrides the Transceive antenna
        # The Transceive ant is always used for TX.
        # If an RX ant is selected then RX out can be selected if required.
        # Each antenna has a frequency range for which it will be selected.
        
        # Make three layouts for the 3 sections
        grid1 = QtGui.QGridLayout()
        grid2 = QtGui.QGridLayout()
        grid3 = QtGui.QGridLayout()
        # Set layouts
        layout.addLayout(grid1, 0,0)
        layout.addLayout(grid2, 1,0)
        layout.addLayout(grid3, 2,0)
        # Pack to the top
        layout.setRowStretch(3,10)
        
        # The top layout has the frequency settings
        low_lbl = QtGui.QLabel('Low')
        grid1.addWidget(low_lbl, 0, 0)
        self.__mhz_start = QtGui.QLineEdit()
        self.__mhz_start.setInputMask('99;_')
        grid1.addWidget(self.__mhz_start, 0, 1)
        high_lbl = QtGui.QLabel('High')
        grid1.addWidget(high_lbl, 0, 2)
        self.__mhz_stop = QtGui.QLineEdit()
        self.__mhz_stop.setInputMask('99;_')
        grid1.addWidget(self.__mhz_stop, 0, 3)
        mhz_lbl = QtGui.QLabel('MHz')
        grid1.addWidget(mhz_lbl, 0, 4)
        
        # Next is the allocations per antenna socket
        # There are six antenna sockets, 3 RX and 3 RX/TX
        # Ant-1 is the default and has a full span.
        # Other allocations may take precedence over Ant-1
        # according to the precedence selection.
        self.__ant_config = [
                ['Ant-1', ANT_1, None, None, None, None],
                ['Ant-2', ANT_2, None, None, None, self.on_set_ant2, None, self.on_clear_ant2],
                ['Ant-3', ANT_3, None, None, None, self.on_set_ant3, None, self.on_clear_ant3],
                ['RX-1', RX_1, None, None, None, self.on_set_rx1, None, self.on_clear_rx1],
                ['RX-2', RX_2, None, None, None, self.on_set_rx2, None, self.on_clear_rx2],
                ['TVR', RX_3, None, None, None, self.on_set_rx3, None, self.on_clear_rx3]
            ]
        for n in range(6):
            lbl = QtGui.QLabel(self.__ant_config[n][0])
            grid2.addWidget(lbl, n, 0)
            if n == 0: min = str(int(MIN_FREQ))
            else:
                if self.__ant_config[n][1] in self.__settings[COMMON][ANTENNA][ANT_SETTING]:
                    min = str(self.__settings[COMMON][ANTENNA][ANT_SETTING][self.__ant_config[n][1]][0])
                else: min = '-'
            self.__ant_config[n][2] = QtGui.QLabel(min)
            grid2.addWidget(self.__ant_config[n][2], n, 1)
            if n == 0: max = str(int(MAX_FREQ))
            else:
                if self.__ant_config[n][1] in self.__settings[COMMON][ANTENNA][ANT_SETTING]:
                    max = str(self.__settings[COMMON][ANTENNA][ANT_SETTING][self.__ant_config[n][1]][1])
                else: max = '-'
            self.__ant_config[n][3] = QtGui.QLabel(max)
            grid2.addWidget(self.__ant_config[n][3], n, 2)
            if n == 0:
                lbl = QtGui.QLabel('Default')
                grid2.addWidget(lbl, n, 3)
            else:
                self.__ant_config[n][4] = QtGui.QPushButton('Set')
                grid2.addWidget(self.__ant_config[n][4], n, 3)
                self.__ant_config[n][4].clicked.connect(self.__ant_config[n][5])
                self.__ant_config[n][6] = QtGui.QPushButton('Clear')
                grid2.addWidget(self.__ant_config[n][6], n, 4)
                self.__ant_config[n][6].clicked.connect(self.__ant_config[n][7])
         
        # Finally the precedence selection
        self.__use_default_cb = QtGui.QCheckBox('Default only')
        grid3.addWidget(self.__use_default_cb, 0, 0)
        self.__use_default_cb.stateChanged.connect(self.on_default)
        self.__use_ant_cb = QtGui.QCheckBox('Ant select only')
        grid3.addWidget(self.__use_ant_cb, 0, 1)
        self.__use_ant_cb.stateChanged.connect(self.on_ant)
        self.__use_antrx_cb = QtGui.QCheckBox('Ant/RX select')
        grid3.addWidget(self.__use_antrx_cb, 0, 2)
        self.__use_antrx_cb.stateChanged.connect(self.on_antrx)
        if self.__settings[COMMON][ANTENNA][ANT_PREC] == ANT_DEFAULT: self.on_default(1)
        elif self.__settings[COMMON][ANTENNA][ANT_PREC] == ANT_ANT: self.on_ant(1)
        elif self.__settings[COMMON][ANTENNA][ANT_PREC] == ANT_ANTRX: self.on_antrx(1)
                    
    def on_set_ant2(self):
        try:
            start = int(self.__mhz_start.text())
            stop = int(self.__mhz_stop.text())
        except:
            start = int(MIN_FREQ)
            stop = int(MAX_FREQ)
        self.__ant_config[1][2].setText(str(start))
        self.__ant_config[1][3].setText(str(stop))
        self.__settings[COMMON][ANTENNA][ANT_SETTING][ANT_2] = [start, stop]
        self.__hints.append(ANTENNA_EVNT)
    
    def on_clear_ant2(self):
        self.__ant_config[1][2].setText('-')
        self.__ant_config[1][3].setText('-')
        del self.__settings[COMMON][ANTENNA][ANT_SETTING][ANT_2]
        self.__hints.append(ANTENNA_EVNT)
        
    def on_set_ant3(self):
        try:
            start = int(self.__mhz_start.text())
            stop = int(self.__mhz_stop.text())
        except:
            start = int(MIN_FREQ)
            stop = int(MAX_FREQ)
        self.__ant_config[2][2].setText(str(start))
        self.__ant_config[2][3].setText(str(stop))
        self.__settings[COMMON][ANTENNA][ANT_SETTING][ANT_3] = [start, stop]
        self.__hints.append(ANTENNA_EVNT)
    
    def on_clear_ant3(self):
        self.__ant_config[2][2].setText('-')
        self.__ant_config[2][3].setText('-')
        del self.__settings[COMMON][ANTENNA][ANT_SETTING][ANT_3]
        self.__hints.append(ANTENNA_EVNT)
        
    def on_set_rx1(self):
        try:
            start = int(self.__mhz_start.text())
            stop = int(self.__mhz_stop.text())
        except:
            start = int(MIN_FREQ)
            stop = int(MAX_FREQ)
        self.__ant_config[3][2].setText(str(start))
        self.__ant_config[3][3].setText(str(stop))
        self.__settings[COMMON][ANTENNA][ANT_SETTING][RX_1] = [start, stop]
        self.__hints.append(ANTENNA_EVNT)
    
    def on_clear_rx1(self):
        self.__ant_config[3][2].setText('-')
        self.__ant_config[3][3].setText('-')
        del self.__settings[COMMON][ANTENNA][ANT_SETTING][RX_1]
        self.__hints.append(ANTENNA_EVNT)

    def on_set_rx2(self):
        try:
            start = int(self.__mhz_start.text())
            stop = int(self.__mhz_stop.text())
        except:
            start = int(MIN_FREQ)
            stop = int(MAX_FREQ)
        self.__ant_config[4][2].setText(str(start))
        self.__ant_config[4][3].setText(str(stop))
        self.__settings[COMMON][ANTENNA][ANT_SETTING][RX_2] = [start, stop]
        self.__hints.append(ANTENNA_EVNT)
    
    def on_clear_rx2(self):
        self.__ant_config[4][2].setText('-')
        self.__ant_config[4][3].setText('-')
        del self.__settings[COMMON][ANTENNA][ANT_SETTING][RX_2]
        self.__hints.append(ANTENNA_EVNT)
        
    def on_set_rx3(self):
        try:
            start = int(self.__mhz_start.text())
            stop = int(self.__mhz_stop.text())
        except:
            start = int(MIN_FREQ)
            stop = int(MAX_FREQ)
        self.__ant_config[5][2].setText(str(start))
        self.__ant_config[5][3].setText(str(stop))
        self.__settings[COMMON][ANTENNA][ANT_SETTING][RX_3] = [start, stop]
        self.__hints.append(ANTENNA_EVNT)
     
    def on_clear_rx3(self):
        self.__ant_config[5][2].setText('-')
        self.__ant_config[5][3].setText('-')
        del self.__settings[COMMON][ANTENNA][ANT_SETTING][RX_3]
        self.__hints.append(ANTENNA_EVNT)
        
    def on_default(self, state):
        
        if not self.__suppress_events:
            self.__suppress_events = True
            self.__use_default_cb.setChecked(1)
            self.__use_ant_cb.setChecked(0)
            self.__use_antrx_cb.setChecked(0)
            self.__settings[COMMON][ANTENNA][ANT_PREC] = ANT_DEFAULT
            self.__hints.append(ANTENNA_EVNT)
            self.__suppress_events = False
        
    def on_ant(self, state):
        
        if not self.__suppress_events:
            self.__suppress_events = True
            self.__use_default_cb.setChecked(0)
            self.__use_ant_cb.setChecked(1)
            self.__use_antrx_cb.setChecked(0)
            self.__settings[COMMON][ANTENNA][ANT_PREC] = ANT_ANT
            self.__hints.append(ANTENNA_EVNT)
            self.__suppress_events = False
        
    def on_antrx(self, state):
        
        if not self.__suppress_events:
            self.__suppress_events = True
            self.__use_default_cb.setChecked(0)
            self.__use_ant_cb.setChecked(0)
            self.__use_antrx_cb.setChecked(1)
            self.__settings[COMMON][ANTENNA][ANT_PREC] = ANT_ANTRX
            self.__hints.append(ANTENNA_EVNT)
            self.__suppress_events = False
    
    #==========================================================================
    def __populate_rx_displays(self, layout):
       
        # This will force the visible rows to no stretch
        # and give col 1 all the available space
        layout.setRowStretch(5,10)
        layout.setColumnStretch(1,10)
        
        # Av mode selection
        av_label = QtGui.QLabel('Average mode')
        layout.addWidget(av_label, 0, 0)
        av_combo = QtGui.QComboBox()
        modes = []
        for mode in pan_mode_lookup:
            modes.append(mode[1])            
        av_combo.addItems(modes)
        av_combo.setCurrentIndex(self.__settings[RX][DISPLAYS][PAN_MODE])
        layout.addWidget(av_combo, 0, 1)
        av_combo.activated.connect(self.on_av_mode)
        
        # Av mode frames
        frm_label = QtGui.QLabel('Over')
        layout.addWidget(frm_label, 1, 0)
        frm_combo = QtGui.QComboBox()
        frm_combo.addItems(self.OVER_FRMS)
        frm_combo.setCurrentIndex(self.OVER_FRMS.index(str(self.__settings[RX][DISPLAYS][PAN_FRM_AV])))
        layout.addWidget(frm_combo, 1, 1)
        frm_combo.activated.connect(self.on_frames)
        frm_label_1 = QtGui.QLabel('frames')
        layout.addWidget(frm_label_1, 1, 2)
     
        # Window selection
        win_label = QtGui.QLabel('Window')
        layout.addWidget(win_label, 2, 0)
        win_combo = QtGui.QComboBox()
        windows = []
        for window in window_lookup:
            windows.append(window[1])            
        win_combo.addItems(windows)
        win_combo.setCurrentIndex(self.__settings[RX][DISPLAYS][WINDOW_TYPE])
        layout.addWidget(win_combo, 2, 1)
        win_combo.activated.connect(self.on_win_type)
        
        # FFT size
        fft_label = QtGui.QLabel('FFT size')
        layout.addWidget(fft_label, 3, 0)
        fft_combo = QtGui.QComboBox()
        fft_combo.addItems(self.FFT_SZ)
        fft_combo.setCurrentIndex(self.FFT_SZ.index(str(self.__settings[RX][DISPLAYS][PAN_FFT_SZ])))
        layout.addWidget(fft_combo, 3, 1)
        fft_combo.activated.connect(self.on_fft_sz)
        
    def on_av_mode(self, index):
        self.__settings[RX][DISPLAYS][PAN_MODE] = index
        self.__hints.append(DISPLAY_EVNT)
        
    def on_frames(self, index):
        self.__settings[RX][DISPLAYS][PAN_FRM_AV] = int(self.OVER_FRMS[index])
        self.__hints.append(DISPLAY_EVNT)
        
    def on_win_type(self, index):
        self.__settings[RX][DISPLAYS][WINDOW_TYPE] = index
        self.__hints.append(DISPLAY_EVNT)
    
    def on_fft_sz(self, index):
        self.__settings[RX][DISPLAYS][PAN_FFT_SZ] = int(self.FFT_SZ[index])
        self.__hints.append(DISPLAY_EVNT)
    
    #==========================================================================
    def __populate_tx_levels(self, layout):
    
        # This will force the visible rows to no stretch
        # and give col 1 all the available space
        layout.setRowStretch(1,10)
        layout.setColumnStretch(1,10)

        tune_label = QtGui.QLabel('Tune level')
        layout.addWidget(tune_label, 0, 0)
        tune_slider = QtGui.QSlider(QtCore.Qt.Horizontal)
        tune_slider.setTickInterval(50)
        layout.addWidget(tune_slider, 0, 1)
        tune_slider.setValue (self.__settings[TX][LEVELS][TUNE_DRIVE_LEVEL])
        tune_slider.valueChanged.connect(self.on_tune_gain)
    
    def on_tune_gain(self, value):
        
        self.__settings[TX][LEVELS][TUNE_DRIVE_LEVEL] = value
        self.__hints.append(LEVELS)
                